﻿using ClumsyWords.Portable.Model;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;

namespace ClumsyWords.DataModel
{

    public class DefinitionsDataItem
    {
        public DefinitionsDataItem()
            : this(String.Empty, new ObservableCollection<CommonGroup<TermProperties>>(), Guid.NewGuid())
        {
        }

        public DefinitionsDataItem(ObservableCollection<CommonGroup<TermProperties>> items)
            : this(String.Empty, items, Guid.NewGuid())
        {
        }

        public DefinitionsDataItem(string term, ObservableCollection<CommonGroup<TermProperties>> items)
            : this(term, items, Guid.NewGuid())
        {
        }

        [JsonConstructor]
        public DefinitionsDataItem(string term, ObservableCollection<CommonGroup<TermProperties>> items, Guid id)
        {
            this.Id = id;
            this.Items = items;

            if (!String.IsNullOrWhiteSpace(term) || term == String.Empty)
                this.Term = term;
            else
                this.Term = this.FirstDefinition.Term;
        }

        public DefinitionsDataItem(DefinitionsDataItem item)
            :this(item.Term, item.Items)
        {
        }

        [JsonProperty]
        public Guid Id { get; set; }

        [JsonProperty]
        public string Term { get; set; }

        [JsonIgnore]
        public TermProperties FirstDefinition
        {
            get { return this.Items.FirstOrDefault().Items.FirstOrDefault(); }
        }

        public int DefinitionsCount
        {
            get 
            {
                int count = 0;
                foreach (var item in this.Items)
                {
                    count += item.Items.Count;
                }

                return count;
            }
        }

        [JsonProperty]
        public ObservableCollection<CommonGroup<TermProperties>> Items { get; set; }

        public void ComposeItemFromTermProperties(string term, List<TermProperties> termProps)
        {
            this.Items = DefinitionsDataItem.GetGroupsList(termProps, p => p.PartOfSpeech);
            this.Term = term;
        }

        public static ObservableCollection<CommonGroup<TermProperties>> GetGroupsList(List<TermProperties> itemList, Func<TermProperties, PartOfSpeech> getKeyFunc)
        {
            IEnumerable<CommonGroup<TermProperties>> groupsList = from item in itemList
                                              group item by getKeyFunc(item) into g
                                              orderby g.Key
                                              select new CommonGroup<TermProperties>(g.Key.ToString(), g.Key.ToString(), g.AsEnumerable<TermProperties>(), true);

            return new ObservableCollection<CommonGroup<TermProperties>>(groupsList);
        }
    }

    public class DefinitionsDataGroup : CommonGroup<DefinitionsDataItem>
    {
        public DefinitionsDataGroup(string key, string title)
            : base(key, title)
        {
        }

        [JsonConstructor]
        public DefinitionsDataGroup(string key, string title, ObservableCollection<DefinitionsDataItem> items, bool isTopItemsSupported)
            : base(key, title, items, isTopItemsSupported)
        {
        }

        public bool ContainsSimilar(DefinitionsDataItem compare)
        {
            foreach (var item in this.Items)
            {
                if (item.Items == compare.Items && item.Term == compare.Term)
                    return true;
            }
            return false;
        }
    }

    public sealed class DefinitionsDataSource
    {
        #region Singleton Pattern
        private static DefinitionsDataSource _instance = new DefinitionsDataSource();

        private DefinitionsDataSource() { }

        public static DefinitionsDataSource Instance
        {
            get
            {
                return _instance;
            }
            private set
            {
                _instance = value;
            }
        }
        #endregion

        private static readonly StorageFolder localFolder = ApplicationData.Current.LocalFolder;

        private Dictionary<string, DefinitionsDataGroup> groupsMap = new Dictionary<string, DefinitionsDataGroup>();

        public IEnumerable<DefinitionsDataGroup> GetGroups(string key)
        {
            var groupKeys = key.Split(' ');
            List<DefinitionsDataGroup> groups = new List<DefinitionsDataGroup>();
            foreach (var k in groupKeys)
            {
                groups.Add(DefinitionsDataSource.Instance.GetGroup(k));
            }
            return groups;
        }

        public DefinitionsDataGroup GetGroup(string key)
        {
            DefinitionsDataGroup ddg;
            if (!groupsMap.TryGetValue(key, out ddg))
            {
                ddg = new DefinitionsDataGroup(key, key);
                groupsMap.Add(key, ddg);
            }

            return ddg;
        }

        public DefinitionsDataItem GetItem(Guid id)
        {

            foreach (var group in groupsMap.Values)
            {
                var match = group.Items.FirstOrDefault(item => item.Id.Equals(id));
                if (match != null) return match;
            }
            // Simple linear search is acceptable for small data sets
            //var matches = groupsMap. .SelectMany(group => group.Items).Where((item) => item.Id.Equals(id));
            //if (matches.Count() == 1) return matches.First();
            return null;
        }

        /// <summary>
        /// Gets all available terms from the specified group key
        /// </summary>
        /// <returns>A list of string representing the collection of terms</returns>
        public List<string> GetTerms(string key)
        {
            List<string> terms = new List<string>();
            foreach (var group in Instance.GetGroups(key))
            {
                foreach (var item in group.Items)
                {
                    terms.Add(item.Term);
                }
            }

            return terms;
        }

        public bool ContainsTerm(string key, string term)
        {
            foreach (var t in Instance.GetTerms(key))
            {
                if (t == term) return true;
            }
            return false;
        }


        /// <summary>
        /// Searches for an item using the SearchSevice and adds this item to the Recent group
        /// </summary>
        /// <param name="url">The url of the STANDS4 Definitions API with the necessary query parameters</param>
        /// <returns>The unique identifier of the new item as a string</returns>
        public async Task<Guid> SearchAsync(string url, string term)
        {
            var item = await SearchService.GetSearchResultAsync(url, term);
            if (item != null)
            {
                DefinitionsDataSource.Instance.GetGroup("Recent").Items.Add(item);
                return item.Id;
            }

            return new Guid();
        }

        public async Task LoadLocalDataAsync()
        {
            bool exists = true;
            StorageFile file = null;
            try
            {
                file = await DefinitionsDataSource.localFolder.GetFileAsync("definitions.txt");
            }
            catch (FileNotFoundException)
            {
                exists = false;
            }
            if (!exists)
            {
                file = await DefinitionsDataSource.localFolder.CreateFileAsync("definitions.txt");
                return;
            }
            var result = await FileIO.ReadTextAsync(file);

            DefinitionsDataSource.Instance.groupsMap = await JsonConvert.DeserializeObjectAsync<Dictionary<string, DefinitionsDataGroup>>(result);
            if (DefinitionsDataSource.Instance.groupsMap == null)
                DefinitionsDataSource.Instance.groupsMap = new Dictionary<string, DefinitionsDataGroup>();
        }

        public async Task SaveLocalDataAsync()
        {
            var file = await DefinitionsDataSource.localFolder.CreateFileAsync("definitions.txt", CreationCollisionOption.ReplaceExisting);

            string jsonData = await JsonConvert.SerializeObjectAsync(DefinitionsDataSource.Instance.groupsMap, Formatting.None);
            await FileIO.WriteTextAsync(file, jsonData);
        }
    }
}
